#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <string.h>
#define _GNU_SOURCE
#include <getopt.h>
#include <stdarg.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <syslog.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#define _LINUX_TIME_H
#include <linux/cn_proc.h>

#include "public_relate.h"

static char *etcpath = "/etc/relate";
/* line number of "etcpath" */
static int line;
LIST_HEAD(entry_head);

struct entry{
	char name[256];
	char *path[254];
	int numaid;
	struct list_head list;
};


extern char *csver;
extern void show_lbhelp(void);
char *procname = NULL;
static unsigned int optflag = 0;
#define delflag 1<<1
#define pidflag 1<<2
#define nameflag 1<<3
#define rnameflag 1<<4
#define listflag 1<<5
#define numaflag 1<<6
#define onflag      1<<7
#define offlag      1<<8
int debugflag; 
static char *namep = NULL;
static char *rnamep = NULL;
static char *pidp= NULL;
static char *numaidp= NULL;


static char *shortopt = ":Dp:m:n:dhvlLr:";
#define NOARG 	no_argument
#define HASARG 	required_argument
#define OTHER 	optional_argument
static struct option longopt[] = {
    {"del", NOARG, NULL, 'd'},
    {"list", NOARG, NULL, 'l'},
    {"help", NOARG, NULL, 'h'},
    {"version", NOARG, NULL, 'v'},
    {"pid", HASARG, NULL, 'p'},
    {"name", HASARG, NULL, 'n'},
    {"numa", HASARG, NULL, 'm'},
    {"relate", HASARG, NULL, 'r'},
    {"load", NOARG, NULL, 'L'},
    {"debug",  NOARG,  NULL, 'D'},
    {"off",  NOARG,  NULL, 0},
    {"on",  NOARG,  NULL, 1},
    {"reset",  NOARG,  NULL, 2},
    {0, 0, 0, 0},
};

/* for error feed back from lbdaemon */
#define PORT 6543
int sock_fd;
struct sockaddr_in my_addr;
struct reterr {
	int err;
};
char buf[MAX_MSGSIZE];
/************************************/

static int is_exist(const char *path)
{
    struct stat st;
    int i;
    int old_errno = errno;
    i = stat(path, &st);
    errno = old_errno;
    return i<0 ? 0 : 1;
}
static void ck_error(int status, int errnum, char *fmt, ...)
{
	va_list args;
	char *strerr;

	if (errnum)
	{
		va_start(args, fmt);
		vfprintf(stderr, fmt, args);
		va_end(args);
		if (!status)	// user defined message
			goto out;
		switch(errnum)
		{
            case ENOENT:
                strerr = "Use K2OS!";
                break;
			default:
				strerr = strerror(errnum);
				break;
		}
		fprintf(stderr, "%s\n", strerr);
	}
out:
	exit(errnum);
}

/* list relationships */
/* Write string to file at given filepath.  Create or truncate file. */
static int send_uc_message(struct user_message *uc, size_t size)
{
	int sd;
	struct sockaddr_nl daddr;
	struct sockaddr_in in_addr;
	int group;
	struct nlmsghdr *nlhdr = NULL;
	struct msghdr msg;
	struct iovec iov;
	struct cn_msg *cnmsg;
	int ret;

	uc->magic = RELATE_MAGIC;
	group = CN_IDX_RELATE;
	sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_CONNECTOR);
	if (sd < 0)
	{
        ck_error(1, errno, "%s: ", procname);
		return 0;
	}
	nlhdr = (struct nlmsghdr *) malloc (NLMSG_SPACE (MAX_MSGSIZE));
	if (nlhdr == NULL)
		goto close_ret;
	daddr.nl_family = AF_NETLINK;
	daddr.nl_pid = 0;
	daddr.nl_groups = CN_IDX_RELATE;
	if (bind(sd, (struct sockaddr *) &daddr, sizeof (daddr)) < 0)
	{
		free(nlhdr);
	  	close (sd);
        ck_error(1, errno, "%s: ", procname);
	  	return 0;
	}
	ret = setsockopt(sd, SOL_NETLINK,1, &group, sizeof(group));
	if (ret < 0)
	{
		perror("setsocket error");
	}
	memset (nlhdr, 0, sizeof (NLMSG_SPACE (MAX_MSGSIZE)));
	memset (&iov, 0, sizeof (struct iovec));
	memset (&msg, 0, sizeof (struct msghdr));
	cnmsg = (struct cn_msg *) NLMSG_DATA (nlhdr);
	memcpy(cnmsg->data, uc, size);
	nlhdr->nlmsg_len = NLMSG_LENGTH (sizeof (struct cn_msg) + size);
	nlhdr->nlmsg_pid = getpid();
	nlhdr->nlmsg_flags = 0;
	cnmsg->id.idx = CN_IDX_RELATE;
	cnmsg->id.val = CN_VAL_RELATE;
	cnmsg->seq = 0;
	cnmsg->ack = 0;
	cnmsg->len = size;	/* user_message length */
	iov.iov_base = (void *) nlhdr;
	iov.iov_len = nlhdr->nlmsg_len;
	msg.msg_name = (void *) &daddr;
	msg.msg_namelen = sizeof (daddr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	ret = sendmsg(sd, &msg, 0);
	close(sd);
	free(nlhdr);
	return 0;
close_ret:
	close(sd);
	free(nlhdr);
	/* ck_error contain an "exit" */
    ck_error(1, errno, "%s: ", procname);
	return 0;
}

static void lb_list(void)
{
    char *end;
    char buf[PATH_MAX];
    int pid;
    int ret;
    FILE *fp;
    struct user_message uc;
    uc.debug = debugflag;   /* 1 or 0 */
    if (optflag & pidflag)
    {
        snprintf(buf, PATH_MAX, "/proc/%s", pidp);
        if (!is_exist(buf))
            ck_error(1, ESRCH, "%s: %s: ", procname, pidp);
        pid = strtol(pidp, &end, 10);
		uc.what = USER_SHOW_LIST_PID;
		uc.u.show.pid = pid;
    } else {
		uc.what = USER_SHOW_LIST_ALL;
    }
    pid = getpid();
	uc.u.show.request_pid = pid;

    /* create a pipe file at /tmp/relate-pid */
    snprintf(uc.u.show.path, RELEN, "/var/tmp/k2os_relate_pipe");
    ret = mkfifo(uc.u.show.path, 0644);
    if (ret < 0)
    {
        if ( errno == EEXIST)
            errno = 0;
        else
            goto out;
    }

    /* send message */
    send_uc_message(&uc, sizeof(uc));
    if ( (fp = fopen(uc.u.show.path, "r")) == NULL )
    {
        fprintf(stderr, "Can't open return message!\n");
		exit(-1);
    }
    /* show the result */
    while (fgets(buf, PATH_MAX, fp) != NULL) 
        fprintf(stdout, "%s", buf);
    unlink(uc.u.show.path);

out:
    fclose(fp);
    /* delete the fifo file */
    ck_error(1, errno, "%s: ", procname);
}

/* del relationships */
static void lb_del(void)
{
    char *end;
    char buf[PATH_MAX];
    int pid;
    struct user_message uc;
    if (optflag & pidflag)
    {
        snprintf(buf, PATH_MAX, "/proc/%s", pidp);
        if (!is_exist(buf))
            ck_error(1, ESRCH, "%s: %s: ", procname, pidp);
        pid = strtol(pidp, &end, 10);
		uc.what = USER_DEL_PID;
		uc.u.udpn.pid = pid;
    }else {
        /* with -d -n flag, namep should a FULLPATH of a program name */
        //TODO: check namep is EXCUTABLE
        strncpy(uc.u.udpn.path, namep, RELEN);
		uc.what = USER_DEL_NAME;
    }
    send_uc_message(&uc, sizeof(uc));
    ck_error(1, errno, "%s: ", procname);
}

static int is_numaonline(int id, char *sid)
{
	char buf[PATH_MAX];
	if (sid != NULL)
		snprintf(buf, PATH_MAX, "/sys/devices/system/node/node%s", sid);
	else
		snprintf(buf, PATH_MAX, "/sys/devices/system/node/node%d", id);
	if (!is_exist(buf))
		return 0;
	return 1;
}

static void lb_add(void)
{
    char *end;
    char buf[PATH_MAX];
    int id;
    struct user_message uc;
	switch (optflag)
	{
        case nameflag|pidflag:
			snprintf(buf, PATH_MAX, "/proc/%s", pidp);
			if (!is_exist(buf))
				ck_error(1, ESRCH, "%s: %s: ", procname, pidp);
			id = strtol(pidp, &end, 10);
			uc.what = USER_ADD_NAME_P;
			uc.u.uanp.pid = id;
			strncpy(uc.u.uanp.name, namep, RELEN);
			break;
        case nameflag|numaflag:
			if (!is_numaonline(0, numaidp))
				ck_error(0, -1, "%s: Numaid error\n", procname);
			id = strtol(numaidp, &end, 10);
			uc.what = USER_SET_NAME_N;
			uc.u.uann.numaid =  id;
			strncpy(uc.u.uann.name, namep, RELEN);
			break;
        case nameflag|rnameflag:
		case nameflag|rnameflag|numaflag:
			//TODO: check rname is EXECUTABLE
			uc.what = USER_ADD_NAME_R;
			strncpy(uc.u.uanr.rname, rnamep, RELEN);
			strncpy(uc.u.uanr.name, namep, RELEN);
        	if (optflag == (nameflag|rnameflag|numaflag) )
			{
				if (!is_numaonline(0, numaidp))
					ck_error(0, -1, "%s: Numaid error\n", procname);
				id = strtol(numaidp, &end, 10);
				uc.u.uanr.numaid = id;
			}else{
				uc.u.uanr.numaid = -1;
			}
			break;
		default:
			break;
	}
    send_uc_message(&uc, sizeof(uc));
    ck_error(1, errno, "%s: ", procname);
}

/* set task of PID to run int NUMAID */

static void lb_setnuma(void)
{
    char *end;
    char buf[PATH_MAX];
    int val;
    struct user_message uc;

    snprintf(buf, PATH_MAX, "/proc/%s", pidp);
    if (!is_exist(buf))
        ck_error(1, ESRCH, "%s: %s: ", procname, pidp);
    val = strtol(pidp, &end, 10);
    uc.u.uapn.pid = val;
    
    snprintf(buf, PATH_MAX, "/sys/devices/system/node/node%s", numaidp);
    if (!is_exist(buf))
   		ck_error(0, -1, "%s: Numaid error\n", procname);
    val = strtol(numaidp, &end, 10);
    uc.u.uapn.numaid = val;

    send_uc_message(&uc, sizeof(uc));
    ck_error(1, errno, "%s: ", procname);
}

static void reset(void)
{
    struct user_message uc;
    uc.what = USER_RESET;
    send_uc_message(&uc, sizeof(uc));
    ck_error(1, errno, "%s: ", procname);
}

static void set_record(int command)
{
    struct user_message uc;
    uc.what = command;
    send_uc_message(&uc, sizeof(uc));
    ck_error(1, errno, "%s: ", procname);
}

static struct entry* getentry(void)
{
	struct entry *e = malloc(sizeof(struct entry));
	memset(e, 0, sizeof(struct entry));
    e->numaid = -1;
	list_add(&e->list, &entry_head);
	return e;
}

static int name_exist(char *name)
{
	if(!name)
		return 0;
	struct entry *en;
	char *p;
	int i;
	list_for_each_entry_rcu(en, &entry_head, list)
	{
		for(i=0, p=en->path[i]; p!=NULL; p=en->path[++i])
		{
			if (!strcmp(name, p))
				return 1;
		}
	}

	return 0;
}

static void parse( FILE *fp, struct entry *en, int index)
{
	struct entry *e;
	int k;
	char buf[256];
	char *p;
	char *endptr;
	while( (p = fgets(buf, 256, fp)) != NULL )
	{
		++line;
		buf[strlen(buf)-1] = '\0';	// \n -> \0
		/* skip leading blank */
		k = strspn(buf, " \t");
		if (buf[k] == '#')
			continue;
		/* omit Port */
		if (!strncmp("Port", buf+k, 4))
			continue;
		/* if leading with RELATENAME */
		if (!strncmp("RELATENAME", buf+k, 10))
		{
			e = getentry();
			k += 10; //10 = strlen("RELATENAME");
			k += strspn(buf+k, " \t");
			if (buf[k] != '=')
			{
				syslog(LOG_USER|LOG_INFO, "%s: error on line %d: unknow symbol\n", procname, line);
				fprintf(stderr, "%s: error on line %d: unknow symbol\n", procname, line);
				exit(0);
			}
			k += 1;	//1=LEN'='
			k += strspn(buf+k, " \t");
			strncpy(e->name, buf+k, 245-k);
			parse(fp, e, 0);
			continue;
		}
		/* if leading with NUMAID */
		if (!strncmp("NUMAID", buf+k, 6))
		{
			if (en == NULL)
			{
			    syslog(LOG_USER|LOG_INFO, "%s: error on line %d\n", procname, line);
				fprintf(stderr,"no config RELATENAME of NUMAID, at line: %d", line);
				exit(0);
			}
			k += 6;//strlen(NUMAID) == 6
			k += strspn(buf+k, " \t");
			if (buf[k] != '=')
			{
				syslog(LOG_USER|LOG_INFO, "%s: error on line %d: unknow symbol\n", procname, line);
				fprintf(stderr, "%s: error on line %d: unknow symbol\n", procname, line);
				exit(0);
			}
			k += 1;	//1=strlen("=")
			k += strspn(buf+k, " \t");
			if (!isdigit(buf[k]) && buf[k] != '-')
			{
				syslog(LOG_USER|LOG_INFO, "%s: error on line %d: unknow symbol\n", procname, line);
				fprintf(stderr, "%s: error on line %d: unknow symbol\n", procname, line);
				exit(0);
			}
			en->numaid = strtol(buf+k, &endptr, 10);
			return;
		}
		if (en == NULL)
			continue;
		if (name_exist(buf+k))
		{
			syslog(LOG_USER|LOG_INFO, "%s: error on line %d: dumplicated\n", procname, line);
			fprintf(stderr, "%s: error on line %d: dumplicated\n", procname, line);
			exit(0);
		}
		else
			en->path[index++] = strdup(buf+k);
	}
	return;
}

static void reload_config(void)
{
    FILE *fp;
	struct entry *en;
	char *p;
	int i=0;
    struct user_message uc;
    if ( (fp = fopen(etcpath, "r")) == NULL)
    {
        syslog(LOG_USER|LOG_INFO, "%s: can't open etcfile: %s\n", procname, etcpath);
        fprintf(stderr, "%s: can't open etcfile: %s\n", procname, etcpath);
        exit(0);
    }
    parse(fp, NULL, 0);
    //TODO: send command to lbdaemon to reset
	list_for_each_entry_rcu(en, &entry_head, list)
	{
		uc.what = USER_SET_NAME_N;
		uc.u.uann.numaid =  -1;
		strncpy(uc.u.uann.name, en->name, RELEN);
    	send_uc_message(&uc, sizeof(uc));
	//	printf("name:%s\n", en->name);
		for(i=0, p=en->path[i]; p!=NULL; p=en->path[++i])
		{
			uc.what = USER_ADD_NAME_R;
			strncpy(uc.u.uanr.name, en->name, RELEN);
			strncpy(uc.u.uanr.rname, p, RELEN);
			send_uc_message(&uc, sizeof(uc));
			//printf("path%d: %s\n", i, en->path[i]);
		}
		uc.what = USER_SET_NAME_N;
		uc.u.uann.numaid =  en->numaid;
		strncpy(uc.u.uann.name, en->name, RELEN);
    	send_uc_message(&uc, sizeof(uc));
		//printf("numa:%d\n", en->numaid);
	}
    exit(0);
}

int lbconfig_main(int argc, char **argv)
{
    int oc;
    int optindex;
    procname = argv[0];
	int pid;
    while ((oc = getopt_long(argc, argv, shortopt, longopt, &optindex)) != -1) {
        switch (oc) {
            case 'm':
                optflag |= numaflag;
                numaidp = optarg;
                break;
            case 'n':
                optflag |= nameflag;
                namep = optarg;
                break;
            case 'p':		// reset
                optflag |= pidflag;
                pidp= optarg;
                break;
            case 'd':
                optflag |= delflag;
                break;
            case 'D':
                debugflag = 1;
                break;
            case 'l':
                optflag |= listflag;
                break;
            case 'v':
                fprintf(stdout, "lbconfig: %s\n", csver);
                exit(0);
                break;
            case 'r':
                optflag |= rnameflag;
                rnamep = optarg;
                break;
            case ':':
                fprintf(stderr, "%s :%s: Need more argument\n", procname, argv[optind - 1]);
                errno = -EINVAL;
                goto err;
            case 'h':
                show_lbhelp();
                break;
            case 'L':
                reload_config();
                break;
            case 0:
                set_record(USER_COMMAND_OFF);
                break;
            case 1:
                set_record(USER_COMMAND_ON);
                break;
            case 2:
                reset();
                break;
            case '?':
            default:
                fprintf(stderr, "%s: %s: Invalid option\n", procname, argv[optind - 1]);
                errno = -EINVAL;
                goto err;
        }
    }
    /* check other option */
    if (optind < argc) {
	fprintf(stderr, "%s: Not option value: ", procname);
	while (optind < argc)
	    fprintf(stderr, "%s ", argv[optind++]);
	fprintf(stderr, "\n");
    errno = EINVAL;
    goto err;
    }

    /* OK, filter the flag*/
    switch(optflag){
        case listflag:
        case listflag|pidflag:
            lb_list();
            break;
        case delflag|pidflag:
        case delflag|nameflag:
            lb_del();
            break;
        case nameflag|pidflag:
        case nameflag|numaflag:
        case nameflag|rnameflag:
        case nameflag|rnameflag|numaflag:
            lb_add();
            break;
        case pidflag | numaflag:
            lb_setnuma();
            break;
        default:
            show_lbhelp();
    }
err:
    exit(errno);
}

int main(int argc, char **argv)
{
	int ret;
	int sd;
	struct sockaddr in_addr;
	socklen_t len;
	int pid;
	//TODO: set a timer for 2 seconds decrements to exit
	if ( (pid = fork()) == 0)
	{
		lbconfig_main(argc, argv);
		exit(0);
	}

	if ( (sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		syslog(LOG_USER|LOG_INFO, "%s: socket initial failure\n", procname);
		exit(EBUSY);
	}
	my_addr.sin_family = AF_INET;
	my_addr.sin_port   = htons(PORT);
	my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	if ( (ret = bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))) < 0)
	{
		syslog(LOG_USER|LOG_INFO, "%s: port %d was opened by others\n", procname, PORT);
		exit(EBUSY);
	}
	listen(sock_fd, 10);

	len = sizeof(struct sockaddr);
	sd = accept(sock_fd, (struct sockaddr*)&in_addr, &len);
	len = recv(sd, buf, MAX_MSGSIZE, 0);
	wait(NULL);
	printf("recv:%s\n", buf);
}
